/*=============================================================================
Copyright (c) 2010 Crytek Studios. All Rights Reserved.

Revision history:
	* Created by Nicolas Schulz, August 2010

=============================================================================*/

#include "StdAfx.h"
#include "GPUTimer.h"
#include "DriverD3D.h"


bool CSimpleGPUTimer::s_bTimingEnabled = false;


void CSimpleGPUTimer::EnableTiming()
{
#ifdef ENABLE_SIMPLE_GPU_TIMERS	
	if( !s_bTimingEnabled )
	{
	#if defined( XENON )	
		D3DPERFCOUNTER_EVENTS perfEvents;
		ZeroMemory( &perfEvents, sizeof( D3DPERFCOUNTER_EVENTS ) );
		perfEvents.CP[0] = GPUPE_CP_COUNT;
		perfEvents.RBBM[0] = GPUPE_RBBM_COUNT;
		perfEvents.RBBM[1] = GPUPE_RBBM_NRT_BUSY;
		g_pd3dDevice->SetPerfCounterEvents( &perfEvents, 0 );
		g_pd3dDevice->EnablePerfCounters( TRUE );
	#endif

		s_bTimingEnabled = true;
	}
#endif
}


void CSimpleGPUTimer::DisableTiming()
{
#ifdef ENABLE_SIMPLE_GPU_TIMERS
	if( s_bTimingEnabled )
	{
	#if defined( XENON )	
		g_pd3dDevice->EnablePerfCounters( FALSE );
	#endif

		s_bTimingEnabled = false;
	}
#endif
}


CSimpleGPUTimer::CSimpleGPUTimer()
{
	m_time = 0.f;
	m_smoothedTime = 0.f;
	m_bInitialized = false;
	m_bWaiting = false;
	m_bStarted = false;

#if defined( WIN32 ) && defined( DIRECT3D9 )
	m_pQueryStart = m_pQueryStop = m_pQueryFreq = NULL;
#elif (defined( WIN32 ) || defined( gringo )) && defined( DIRECT3D10 )
	m_pQueryStart = m_pQueryStop = m_pQueryFreq = NULL;
#elif defined( XENON )
	m_pPerfCntStart = m_pPerfCntStop = NULL;
#elif defined( PS3 )
	m_dpStart = m_dpStop = 0;
#endif
}


CSimpleGPUTimer::~CSimpleGPUTimer()
{
	Release();
}

void CSimpleGPUTimer::Release()
{
#if defined( WIN32 ) && defined( DIRECT3D9 )
	SAFE_RELEASE( m_pQueryStart );
	SAFE_RELEASE( m_pQueryStop );
	SAFE_RELEASE( m_pQueryFreq );
#elif (defined( WIN32 ) || defined( gringo )) && defined( DIRECT3D10 )
	SAFE_RELEASE( m_pQueryStart );
	SAFE_RELEASE( m_pQueryStop );
	SAFE_RELEASE( m_pQueryFreq );
#elif defined( XENON )
	SAFE_RELEASE( m_pPerfCntStart );
	SAFE_RELEASE( m_pPerfCntStop );
#endif
	m_bInitialized = false;
}

bool CSimpleGPUTimer::Init()
{
#ifdef ENABLE_SIMPLE_GPU_TIMERS
	if( !m_bInitialized )
	{
	#if defined( WIN32 ) && defined( DIRECT3D9 )
		if( gcpRendD3D->m_pd3dDevice->CreateQuery( D3DQUERYTYPE_TIMESTAMPFREQ, &m_pQueryFreq ) == S_OK &&
		    gcpRendD3D->m_pd3dDevice->CreateQuery( D3DQUERYTYPE_TIMESTAMP, &m_pQueryStart ) == S_OK &&
		    gcpRendD3D->m_pd3dDevice->CreateQuery( D3DQUERYTYPE_TIMESTAMP, &m_pQueryStop ) == S_OK )
		{
			m_bInitialized = true;
		}
	#elif (defined( WIN32 ) || defined( gringo )) && defined( DIRECT3D10 )
		D3D11_QUERY_DESC stampDisjointDesc = { D3D11_QUERY_TIMESTAMP_DISJOINT, 0 };
		D3D11_QUERY_DESC stampDesc = { D3D11_QUERY_TIMESTAMP, 0 };
		
		if( gcpRendD3D->m_pd3dDevice->CreateQuery( &stampDisjointDesc, &m_pQueryFreq ) == S_OK &&
		    gcpRendD3D->m_pd3dDevice->CreateQuery( &stampDesc, &m_pQueryStart ) == S_OK &&
		    gcpRendD3D->m_pd3dDevice->CreateQuery( &stampDesc, &m_pQueryStop ) == S_OK )
		{
			m_bInitialized = true;
		}
	#elif defined( XENON )
		if( g_pd3dDevice->CreatePerfCounters( &m_pPerfCntStart, 1 ) == S_OK &&
		    g_pd3dDevice->CreatePerfCounters( &m_pPerfCntStop, 1 ) == S_OK )
		{
			m_bInitialized = true;
		}
	#elif defined( PS3 )
		m_bInitialized = true;
	#endif
	}
#endif

	return m_bInitialized;
}


void CSimpleGPUTimer::Start()
{
#ifdef ENABLE_SIMPLE_GPU_TIMERS
	if( s_bTimingEnabled && !m_bWaiting && Init() )
	{
	#if defined( WIN32 ) && defined( DIRECT3D9 )
		if( m_pQueryFreq->Issue( D3DISSUE_END ) == S_OK &&
		    m_pQueryStart->Issue( D3DISSUE_END ) == S_OK )
		{
			m_bStarted = true;
		}
	#elif (defined( WIN32 ) || defined( gringo )) && defined( DIRECT3D10 )
		// TODO: The d3d docs suggest that the disjoint query should be done once per frame at most
		gcpRendD3D->m_pd3dDeviceContext->Begin( m_pQueryFreq );
		gcpRendD3D->m_pd3dDeviceContext->End( m_pQueryStart );
		m_bStarted = true;
	#elif defined( XENON )
		if( g_pd3dDevice->QueryPerfCounters( m_pPerfCntStart, 0 ) == S_OK )
		{
			m_bStarted = true;
		}
	#elif defined( PS3 )
		m_dpStart = gcpRendD3D->m_pd3dDevice->DrawCallHandle()-1;
		m_bStarted = true;
	#endif
	}
#endif
}

void CSimpleGPUTimer::Stop()
{
#ifdef ENABLE_SIMPLE_GPU_TIMERS
	if( s_bTimingEnabled && m_bStarted&& m_bInitialized  )
	{
	#if defined( WIN32 ) && defined( DIRECT3D9 )
		if( m_pQueryStop->Issue( D3DISSUE_END ) == S_OK )
		{
			m_bStarted = false;
			m_bWaiting = true;
		}
	#elif (defined( WIN32 ) || defined( gringo )) && defined( DIRECT3D10 )
		gcpRendD3D->m_pd3dDeviceContext->End( m_pQueryStop );
		gcpRendD3D->m_pd3dDeviceContext->End( m_pQueryFreq );
		m_bStarted = false;
		m_bWaiting = true;
	#elif defined( XENON )
		if( g_pd3dDevice->QueryPerfCounters( m_pPerfCntStop, 0 ) == S_OK )
		{
			m_bStarted = false;
			m_bWaiting = true;
		}
	#elif defined( PS3 )
		m_dpStop = gcpRendD3D->m_pd3dDevice->DrawCallHandle()-1;
		m_bStarted = false;
		m_bWaiting = true;
	#endif
	}
#endif
}

void CSimpleGPUTimer::UpdateTime()
{
#ifdef ENABLE_SIMPLE_GPU_TIMERS
	if( s_bTimingEnabled && m_bWaiting && m_bInitialized )
	{
	#if defined( WIN32 ) && defined( DIRECT3D9 )	
		UINT64 freq, timeStart, timeStop;

		if( m_pQueryFreq->GetData( &freq, sizeof( UINT64 ), D3DGETDATA_FLUSH ) == S_OK &&
		    m_pQueryStart->GetData( &timeStart, sizeof( UINT64 ), D3DGETDATA_FLUSH ) == S_OK &&
		    m_pQueryStop->GetData( &timeStop, sizeof( UINT64 ), D3DGETDATA_FLUSH ) == S_OK )
		{
			m_time = (timeStop - timeStart) / (float)(freq / 1000);
			m_bWaiting = false;
		}
	#elif (defined( WIN32 ) || defined( gringo )) && defined( DIRECT3D10 )	
		D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjointData;
		UINT64 timeStart, timeStop;

		if( gcpRendD3D->m_pd3dDeviceContext->GetData( m_pQueryFreq, &disjointData, m_pQueryFreq->GetDataSize(), 0 ) == S_OK &&
		    gcpRendD3D->m_pd3dDeviceContext->GetData( m_pQueryStart, &timeStart, m_pQueryStart->GetDataSize(), 0 ) == S_OK &&
		    gcpRendD3D->m_pd3dDeviceContext->GetData( m_pQueryStop, &timeStop, m_pQueryStop->GetDataSize(), 0 ) == S_OK )
		{
			if( !disjointData.Disjoint )
			{
				float time = (timeStop - timeStart) / (float)(disjointData.Frequency / 1000);
				if( time >= 0.f && time < 1000.f ) m_time = time;  // Filter out wrong insane values that get reported sometimes
			}
			m_bWaiting = false;
		}
	#elif defined( XENON )
		D3DPERFCOUNTER_VALUES valuesStart, valuesStop;

		if( !m_pPerfCntStart->IsBusy() && !m_pPerfCntStop->IsBusy() )
		{
			if( m_pPerfCntStart->GetValues( &valuesStart, 0, NULL ) == S_OK &&
					m_pPerfCntStop->GetValues( &valuesStop, 0, NULL ) == S_OK )
			{
				m_time = (float)((double)(valuesStop.RBBM[1].QuadPart - valuesStart.RBBM[1].QuadPart) / 500000.0);
				m_bWaiting = false;
			}
		}
	#elif defined( PS3 )
		bool bWaitForGPU = false;
#if defined(ENABLE_ACCURATE_RSX_PROFILING)
		bWaitForGPU = gcpRendD3D->CV_r_ProfileRSX_RT != 0;
#endif
		uint64 timeStart = gcpRendD3D->m_pd3dDevice->TimeStamp( m_dpStart, bWaitForGPU );
		uint64 timeStop = gcpRendD3D->m_pd3dDevice->TimeStamp( m_dpStop, bWaitForGPU );
			
		if( timeStart == ~0ULL || timeStop == ~0ULL )
		{
			// the draw call is too old and the time has been lost
			m_bWaiting = false;
		}
		else if( timeStart != 0 && timeStop != 0 )
		{
			float time = (float)((double)(timeStop - timeStart) / 1000000.0);
			if( time >= 0.f && time < 5000.f ) m_time = time;  // Filter out wrong insane values that get reported sometimes
			m_bWaiting = false;
		}
	#endif

		if( !m_bWaiting )
			m_smoothedTime = m_smoothedTime * 0.7f + m_time * 0.3f;
	}
#endif
}
